import os
from pathlib import Path
from unittest import mock

import pytest
from aioresponses import aioresponses
from pytest_mock import MockerFixture

from checkov.common.bridgecrew.vulnerability_scanning.image_scanner import ImageScanner, CHECKOV_SEC_IN_WEEK
from checkov.common.bridgecrew.vulnerability_scanning.integrations.docker_image_scanning import (
    docker_image_scanning_integration,
)


def get_report_url() -> str:
    base_url = "https://www.bridgecrew.cloud/api/v1/vulnerabilities"
    return f"{base_url}/results"


@pytest.mark.asyncio
async def test_report_results(mocker: MockerFixture, mock_bc_integration, docker_image_scan_result):
    # given
    bc_api_key = "abcd1234-abcd-1234-abcd-1234abcd1234"
    report_url = get_report_url()

    mocker.patch.dict(os.environ, {"BC_ROOT_DIR": "app"})

    # when
    with aioresponses() as m:
        m.post(report_url, status=200)

        result = await docker_image_scanning_integration.report_results_async(
            twistcli_scan_result=docker_image_scan_result,
            bc_platform_integration=mock_bc_integration,
            bc_api_key=bc_api_key,
            file_path=Path("app/Dockerfile"),
            file_content="FROM python:3.7-slim\n",
            docker_image_name="sha256:e70f7611f4d093d5f73026d23f7ab612aa1794abfb210ef1c549b225380d053b",
        )

    # then
    assert result == 0
    assert next(iter(m.requests.values()))[0].kwargs["json"] == {
        "dockerImageName": "sha256:e70f7611f4d093d5f73026d23f7ab612aa1794abfb210ef1c549b225380d053b",
        "dockerFilePath": "/Dockerfile",
        "dockerFileContent": "FROM python:3.7-slim\n",
        "type": "Image",
        "sourceId": "bridgecrewio/checkov",
        "branch": "master",
        "sourceType": "Github",
        "vulnerabilities": [
            {
                "cveId": "CVE-2022-23990",
                "status": "fixed in 2.2.10-2+deb11u1",
                "severity": "critical",
                "packageName": "expat",
                "packageVersion": "2.2.10-2",
                "link": "https://security-tracker.debian.org/tracker/CVE-2022-23990",
                "cvss": 9.8,
                "vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
                "description": "Expat (aka libexpat) before 2.4.4 has an integer overflow in the doProlog function.",
                "riskFactors": [
                    "Attack complexity: low",
                    "Attack vector: network",
                    "Critical severity",
                    "Has fix",
                    "Recent vulnerability",
                ],
                "publishedDate": "2022-01-26T21:15:00+02:00",
            },
            {
                "cveId": "CVE-2022-23852",
                "status": "fixed in 2.2.10-2+deb11u1",
                "severity": "critical",
                "packageName": "expat",
                "packageVersion": "2.2.10-2",
                "link": "https://security-tracker.debian.org/tracker/CVE-2022-23852",
                "cvss": 9.8,
                "vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
                "description": "Expat (aka libexpat) before 2.4.4 has a signed integer overflow in XML_GetBuffer, for configurations with a nonzero XML_CONTEXT_BYTES.",
                "riskFactors": [
                    "Attack complexity: low",
                    "Attack vector: network",
                    "Critical severity",
                    "Has fix",
                    "Recent vulnerability",
                ],
                "publishedDate": "2022-01-24T04:15:00+02:00",
            },
        ],
        "packages": [
            {"type": "os", "name": "mawk", "version": "1.3.4.20200120-2", "licenses": ["GPL-2"]},
            {"type": "os", "name": "gzip", "version": "1.10-4", "licenses": ["GPL-3+"]},
        ],
        "relatedResourceId": None,
        "errorLines": None
    }


@pytest.mark.asyncio
async def test_report_results_with_cicd(mocker: MockerFixture, mock_bc_integration, docker_image_scan_result):
    # given
    bc_api_key = "abcd1234-abcd-1234-abcd-1234abcd1234"
    report_url = get_report_url()
    cicd_details = {
        "runId": 123,
        "pr": "patch-1",
        "commit": "qwerty1234",
    }

    mock_bc_integration.cicd_details = cicd_details
    mocker.patch.dict(os.environ, {"BC_ROOT_DIR": "app"})

    # when
    with aioresponses() as m:
        m.post(report_url, status=200)

        result = await docker_image_scanning_integration.report_results_async(
            twistcli_scan_result=docker_image_scan_result,
            bc_platform_integration=mock_bc_integration,
            bc_api_key=bc_api_key,
            file_path=Path("app/Dockerfile"),
            file_content="FROM python:3.7-slim\n",
            docker_image_name="sha256:e70f7611f4d093d5f73026d23f7ab612aa1794abfb210ef1c549b225380d053b",
        )

    # then
    assert result == 0
    assert next(iter(m.requests.values()))[0].kwargs["json"]["cicdDetails"] == cicd_details


@pytest.mark.asyncio
async def test_report_results_fail(mocker: MockerFixture, mock_bc_integration, docker_image_scan_result):
    # given
    bc_api_key = "abcd1234-abcd-1234-abcd-1234abcd1234"
    report_url = get_report_url()

    mocker.patch.dict(os.environ, {"BC_ROOT_DIR": "app", "REQUEST_MAX_TRIES": "3", "SLEEP_BETWEEN_REQUEST_TRIES": "0.01"})

    # when
    with aioresponses() as m:
        m.post(report_url, status=403, repeat=True)

        result = await docker_image_scanning_integration.report_results_async(
            twistcli_scan_result=docker_image_scan_result,
            bc_platform_integration=mock_bc_integration,
            bc_api_key=bc_api_key,
            file_path=Path("app/Dockerfile"),
            file_content="FROM python:3.7-slim\n",
            docker_image_name="sha256:e70f7611f4d093d5f73026d23f7ab612aa1794abfb210ef1c549b225380d053b",
        )

    # then
    assert result == 1


def test_should_download_new_twistcli(tmp_path: Path):
    # given
    scanner = ImageScanner()
    twistcli_path = tmp_path / "twistcli"
    scanner.twistcli_path = twistcli_path

    # then
    assert scanner.should_download()


@mock.patch.dict(os.environ, {"CHECKOV_EXPIRATION_TIME_IN_SEC": str(CHECKOV_SEC_IN_WEEK)})
def test_not_should_download_twistcli(tmp_path: Path):
    # given
    scanner = ImageScanner()
    twistcli_path = tmp_path / "twistcli"
    twistcli_path.touch()
    scanner.twistcli_path = twistcli_path

    # then
    assert not scanner.should_download()


@mock.patch.dict(os.environ, {"CHECKOV_EXPIRATION_TIME_IN_SEC": "0"})
def test_should_download_twistcli_again(tmp_path: Path):
    # given
    scanner = ImageScanner()
    twistcli_path = tmp_path / "twistcli"
    twistcli_path.touch()
    scanner.twistcli_path = twistcli_path

    # then
    assert scanner.should_download()


def test_cleanup_twistcli_exists(tmp_path: Path):
    # given
    scanner = ImageScanner()

    # prepare local paths
    twistcli_path = tmp_path / "twistcli"
    twistcli_path.touch()
    scanner.twistcli_path = twistcli_path

    # when
    scanner.cleanup_scan()

    # then
    assert not twistcli_path.exists()


def test_cleanup_twistcli_not_exists(tmp_path: Path):
    # given
    scanner = ImageScanner()

    # prepare local paths
    twistcli_path = tmp_path / "twistcli"
    scanner.twistcli_path = twistcli_path

    # when
    scanner.cleanup_scan()

    # then
    assert not twistcli_path.exists()
